package archer.back.controllers

import java.net.URLEncoder
import java.nio.file.Paths
import java.util.UUID

import archer.back.models._
import archer.back.utils.PoiUtils
import play.api.mvc.ControllerComponents
import utils._
import io.circe.syntax._
import io.circe.generic.auto._
import cats.implicits._
import com.sksamuel.elastic4s.{ Hit, HitReader }
import io.circe.{ Decoder, Json }
import io.circe.optics.JsonPath._
import models.TableData
import org.apache.poi.hssf.usermodel.HSSFWorkbook
import org.jsoup.Jsoup
import org.xarcher.emiya.utils.EmbeddedServer
import play.api.http.HeaderNames
import play.api.libs.ws.WSClient

import scala.concurrent.Future
import scala.util.{ Properties, Try }
import scala.collection.JavaConverters._
import com.sksamuel.elastic4s.http.ElasticDsl._
import com.sksamuel.elastic4s.circe._
import com.sksamuel.elastic4s.http.index.CreateIndexResponse
import com.sksamuel.elastic4s.http.search.SearchResponse
import com.sksamuel.elastic4s.http.{ RequestFailure, RequestSuccess }
import com.sksamuel.elastic4s.mappings.dynamictemplate.DynamicMapping
import io.circe.jawn.decode

class UrlsController(
  controllerComponents: ControllerComponents,
  wsClient: WSClient,
  //urlService: UrlService,
  searchService: SearchService,
  embeddedServer: EmbeddedServer,
  customAssets: CustomAssets)
  extends BackController(controllerComponents)
  with ArcherHelper {

  implicit val ec = defaultExecutionContext

  def list(
    actionId: Int,
    include: String,
    exclude: String,
    hostInclude: String,
    hostExclude: String,
    refererInclude: String,
    refererExclude: String,
    protocolInclude: String,
    protocolExclude: String,
    urlInclude: String,
    urlExclude: String,
    typeInclude: String,
    typeExclude: String,
    minSize: String,
    planId: Int,
    page: Int,
    limit: Int) = Action.async { implicit request =>
    def toTableData(r: SearchResponse) = {
      TableData(
        data = r.hits.hits.map(s =>
          s.to[CommonBody].asJsonObject.add("_id", Json.fromString(s.id))),
        count = Option(r.totalHits.toInt))
    }

    val result = etLess.lift {
      val model = etLess.unlift(
        searchService.list(
          actionId = actionId,
          include = include,
          exclude = exclude,
          refererInclude = refererInclude,
          refererExclude = refererExclude,
          hostInclude = hostInclude,
          hostExclude = hostExclude,
          protocolInclude = protocolInclude,
          protocolExclude = protocolExclude,
          urlInclude = urlInclude,
          urlExclude = urlExclude,
          typeInclude = typeInclude,
          typeExclude = typeExclude,
          minSize = minSize,
          planId = planId,
          page = page,
          limit = limit))
      toTableData(model)
      //TableData(data = model.hits.hits.map(s => s.to[CommonBody].asJsonObject.add("_id", Json.fromString(s.id))), count = Option(model.hits.size.toInt))
    }
    R.toJsonResult(result)
  }

  def review(currentId: String) = Action.async { implicit request =>
    val result = etLess.lift {
      val responseT =
        etLess.unlift(commonCatch(embeddedServer.esLocalClient.flatMap {
          client =>
            client.execute(
              get(embeddedServer.index, embeddedServer.typeName, currentId))
        }))
      val responseModel = {
        val response = etLess.unlift(V.fromEither(responseT.left.map(r =>
          ErrorResult(code = r.status, msg = r.error.reason))))

        val response1 =
          Try { response.result.to[CommonBody] }.toEither.left.map(_ =>
            ErrorResult(code = NOT_FOUND, msg = "找不到该 id 对应的页面数据"))
        etLess.unlift(V.fromEither(response1))
      }
      views.html
        .pageReview(responseModel.cssBody.getOrElse(""))(responseModel.url)
    }
    result.fold(err => Ok(err.msg), view => Ok(view))
  }

  def anchor(parentId: String, targetUrl: String) = Action.async {
    implicit request =>
      val targetUrlTrim = targetUrl.trim
      val result = etLess.lift {
        val responseT =
          etLess.unlift(commonCatch(embeddedServer.esLocalClient.flatMap {
            client =>
              client.execute(
                get(embeddedServer.index, embeddedServer.typeName, parentId))
          }))
        val responseModel = {
          val response = etLess.unlift(V.fromEither(responseT.left.map(r =>
            ErrorResult(code = r.status, msg = r.error.reason))))

          val response1 =
            Try { response.result.to[CommonBody] }.toEither.left.map(_ =>
              ErrorResult(code = NOT_FOUND, msg = "找不到该 id 对应的页面数据"))
          etLess.unlift(V.fromEither(response1))
        }
        val confirmDoms = responseModel.subs.filter(_.url == targetUrlTrim)
        views.html.pagePath(responseModel.cssBody.getOrElse(""))(
          confirmDoms.map(_.targetClass).asJson.noSpaces)(targetUrlTrim)(
            responseModel.url)(confirmDoms.size)
      }
      result.fold(err => Ok(err.msg), view => Ok(view))
  }

  def reAnchor(parentUrl: String, targetUrl: String) = Action.async {
    implicit request =>
      val targetUrlTrim = targetUrl.trim
      val parentUrlTrim = parentUrl.trim
      val result = etLess.lift {
        val response =
          etLess.unlift(commonCatch(wsClient.url(parentUrlTrim).get))
        val bodyBytes = response.bodyAsBytes

        val isConfirm =
          Option(response.contentType.startsWith("text/html")).filter(s =>
            s == true)
        val isConfirmEt =
          V.fromOption(
            isConfirm,
            ErrorResult(msg = "目标页面不是 html 页面", code = BAD_REQUEST))
        etLess.unlift(isConfirmEt): Boolean

        val body = bodyBytes.decodeString("utf-8")
        val doc = Jsoup.parse(body, parentUrl)

        val cssPrefix = "lolita-gentleman-"

        val hrefLinks = doc
          .select("*[href]")
          .asScala
          .toList
          .map { dom =>
            val urlOpt =
              Option(dom.attr("abs:href")).map(_.trim).filterNot(_.isEmpty)
            urlOpt.map { s =>
              val newClass = cssPrefix + UUID.randomUUID().toString
              dom.attr("href", s)
              dom.addClass(newClass)
              (s, newClass)
            }
          }
          .collect { case Some(s) => SubUrl(s._1, List.empty, s._2) }
        val srcLinks = doc
          .select("*[src]")
          .asScala
          .toList
          .map { dom =>
            val urlOpt =
              Option(dom.attr("abs:src")).map(_.trim).filterNot(_.isEmpty)
            urlOpt.map { s =>
              val newClass = cssPrefix + UUID.randomUUID().toString
              dom.attr("src", s)
              dom.addClass(newClass)
              (s, newClass)
            }
          }
          .collect { case Some(s) => SubUrl(s._1, List.empty, s._2) }

        val subs = hrefLinks ::: srcLinks
        val confirmDoms = subs.filter(_.url == targetUrlTrim)
        views.html.pagePath(doc.outerHtml)(
          confirmDoms.map(_.targetClass).asJson.noSpaces)(targetUrlTrim)(
            parentUrlTrim)(confirmDoms.size)
      }
      result.fold(err => Ok("未知错误"), view => Ok(view))
  }

  def excelExport(
    actionId: Int,
    include: String,
    exclude: String,
    hostInclude: String,
    hostExclude: String,
    refererInclude: String,
    refererExclude: String,
    protocolInclude: String,
    protocolExclude: String,
    urlInclude: String,
    urlExclude: String,
    typeInclude: String,
    typeExclude: String,
    minSize: String,
    planId: Int) = Action.async { implicit request =>
    etLess
      .lift {
        val workbook = new HSSFWorkbook()
        val dataWrap = etLess.unlift(searchService.list(
          actionId = actionId,
          include = include,
          exclude = exclude,
          refererInclude = refererInclude,
          refererExclude = refererExclude,
          hostInclude = hostInclude,
          hostExclude = hostExclude,
          protocolInclude = protocolInclude,
          protocolExclude = protocolExclude,
          urlInclude = urlInclude,
          urlExclude = urlExclude,
          typeInclude = typeInclude,
          typeExclude = typeExclude,
          minSize = minSize,
          planId = planId,
          page = 1,
          limit = 60000))
        val (dataList, columns) = etLess.unlift(
          searchService.toPoi(dataWrap.to[CommonBody].toList, request.host))
        val path = Paths
          .get(Properties.tmpDir)
          .resolve(UUID.randomUUID.toString)
          .resolve("export.xls")
        PoiUtils.render(dataList)(path)(workbook)("网站详细信息列表")(columns)
        Future.successful(
          Ok.sendPath(path)
            .withHeaders(
              CONTENT_TYPE -> BINARY,
              CONTENT_DISPOSITION -> DownloadUtils
                .contentDispositionByName("网站详细信息列表.xls")))
      }
      .fold(e => Future.successful(Ok(e.msg)), result => result)
      .flatten
  }

  def excelExportWithReferer(
    actionId: Int,
    include: String,
    exclude: String,
    hostInclude: String,
    hostExclude: String,
    refererInclude: String,
    refererExclude: String,
    protocolInclude: String,
    protocolExclude: String,
    urlInclude: String,
    urlExclude: String,
    typeInclude: String,
    typeExclude: String,
    minSize: String,
    planId: Int) = Action.async { implicit request =>
    etLess
      .lift {
        val workbook = new HSSFWorkbook()
        val dataWrap = etLess.unlift(searchService.list(
          actionId = actionId,
          include = include,
          exclude = exclude,
          refererInclude = refererInclude,
          refererExclude = refererExclude,
          hostInclude = hostInclude,
          hostExclude = hostExclude,
          protocolInclude = protocolInclude,
          protocolExclude = protocolExclude,
          urlInclude = urlInclude,
          urlExclude = urlExclude,
          typeInclude = typeInclude,
          typeExclude = typeExclude,
          minSize = minSize,
          planId = planId,
          page = 1,
          limit = 60000))
        val (dataList, columns) = etLess.unlift(
          searchService.toPoiWithReferer(
            dataWrap.to[CommonBody].toList,
            request.host))
        val path = Paths
          .get(Properties.tmpDir)
          .resolve(UUID.randomUUID.toString)
          .resolve("export.xls")
        PoiUtils.render(dataList)(path)(workbook)("网站详细信息列表")(columns)
        Future.successful(
          Ok.sendPath(path)
            .withHeaders(
              CONTENT_TYPE -> BINARY,
              CONTENT_DISPOSITION -> DownloadUtils
                .contentDispositionByName("网站详细信息列表.xls")))
      }
      .fold(e => Future.successful(Ok(e.msg)), result => result)
      .flatten
  }

}
